原始题目:剑指 Offer 07. 重建二叉树 (opens new window)

解题思路:

先序遍历性质:节点按照 【根节点 | 左子树 | 右子树】排序。

中序遍历性质:节点按照 【左子树 | 根节点 | 右子树】排序。

根据以上性质,有以下结论:

  1. 先序遍历的首个元素 vv 为根节点的值。

  2. vv 在中序遍历中的位置,可以将中序遍历数组划分为左子树部分和右子树部分。

  3. 根据结论 22 ,可以计算左子树的节点数,因此可以再将先序遍历划分为左子树部分和右子树部分。

根据以上结论,可以使用递归的方式对所有子树进行划分。

递归函数

递归参数:先序遍历数组 preorderpreorder,先序遍历的区间 preSpreS, preEpreE;中序遍历数组 inorderinorder,中序遍历的区间 inSinSinEinE

终止条件preS>preEpreS > preE 或者 inS>inEinS > inE

递推工作

  1. 根据 preorderpreorderpreSpreS ,可以得到根节点的值 rootVrootV,通过 rootVrootV 拿到其在 inorderinorder 中的位置 rootIdxrootIdx,所以 inorderinorder 中,左子树的区间为 [inS,rootIdx1][inS, rootIdx-1],右子树的区间为 [rootIdx+1,inE][rootIdx + 1, inE]
  2. preorderpreorder 划分左右子树部分。左子树的个数应该为 rootIdxinSrootIdx - inS,所以 preorderpreorder 中左子树的区间为 [preS+1,preS+rootIdxinS][preS + 1, preS + rootIdx - inS],右子树的区间为 [preS+rootIdxinS+1,preE][preS + rootIdx - inS + 1, preE]
  3. 创建根节点,根据 1122 步得到的左右子树区间,继续进行递归,将根节点的左右孩子指向递归函数的返回值。

代码:

public TreeNode buildTree(int[] preorder, int[] inorder) {
    if(preorder == null || inorder == null
            || preorder.length == 0 || preorder.length != inorder.length){
        return null;
    }
    return buildTree(
            preorder, 0, preorder.length - 1,
            inorder, 0, inorder.length - 1);
}

private TreeNode buildTree(int[] preOrder, int preStart, int preEnd,
                           int[] inOrder, int inStart, int inEnd) {
    if (preStart > preEnd || inStart > inEnd) {
        return null;
    }
    int rootVal = preOrder[preStart];
    int rootIdxInOrder = findIndex(inOrder, inStart, inEnd, rootVal);
    TreeNode root = new TreeNode(rootVal);
    // 如何确定 preEnd 的大小?
    // 通过 rootIdxInOrder 其实可以确定 preOrder 中,那部分是属于左子树的
    // 左子树的节点个数为 rootIdxInOrder - inStart,preEnd 应该 preStart + rootIdxInOrder - inStart
    int newPreEnd = preStart + rootIdxInOrder - inStart;
    root.left = buildTree(preOrder, preStart + 1, newPreEnd, inOrder, inStart, rootIdxInOrder - 1);
    root.right = buildTree(preOrder, newPreEnd+1, preEnd, inOrder, rootIdxInOrder + 1, inEnd);
    return root;
}

/**
 * 拿到根节点在中序遍历数组中的索引位置
 */
private int findIndex(int[] inOrder, int s, int e, int root) {
    for (int i = s; i <= e; i++) {
        if (inOrder[i] == root) {
            return i;
        }
    }
    return -1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

复杂度分析

  • 时间复杂度 O(N)O(N):递归共建立 NN 个节点,每层递归中的节点建立占用 O(1)O(1),寻找根节点位置的循环总共占用O(N)O(N),因此使用 O(N)O(N) 时间。

  • 空间复杂度 O(N)O(N) :最差情况下,树是一个链表,需要占用函数栈的空间为 O(N)O(N)。当树为满二叉树时,递归深度为 logNlogN,占用 O(logN)O(logN) 的空间。

上次更新: 2023/10/15